package com.partsoft.dits.ldap;

import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.naming.directory.DirContext;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import com.partsoft.dits.AbstractConfigurableDescriptorManager;
import com.partsoft.dits.log.Log;

public class LdapContextConfigurableManager extends AbstractConfigurableDescriptorManager<LdapContextDescriptor>
		implements ApplicationContextAware, ApplicationListener<ApplicationEvent>, LdapContextSourceProvider,
		LdapContextSourceManager, LdapContextProvider, InitializingBean {

	private static String defaultCaStore;

	static {
		defaultCaStore = new StringBuilder(System.getProperty("java.home")).append(File.separator).append("lib")
				.append(File.separator).append("security").append(File.separator).append("cacerts").toString();
	}

	private String caStoreLocation;

	private Map<String, ContextSource> ldapContextPoolMap = Collections
			.synchronizedMap(new HashMap<String, ContextSource>(5));

	public String getCaStoreLocation() {
		return caStoreLocation;
	}

	public void setCaStoreLocation(String caStoreLocation) {
		this.caStoreLocation = caStoreLocation;
	}

	public Class<LdapContextDescriptor> getSupportClass() {
		return LdapContextDescriptor.class;
	}

	protected ContextSource doAddFactoryToPool(LdapContextDescriptor desc) {
		ContextSource mto = null;
		synchronized (this) {
			LdapContextSource lcs  = desc.createContextSource();
			/*
			PoolingContextSource cs = new PoolingContextSource();
			
			cs.setContextSource(lcs);
			cs.setDirContextValidator(new DefaultDirContextValidator(SearchControls.SUBTREE_SCOPE));
			cs.setMaxActive(desc.getPoolMaxActive());
			cs.setMaxIdle(desc.getPoolMaxIdle());
			cs.setMinIdle(desc.getPoolMinIdle());
			cs.setTestWhileIdle(true);
			cs.setTestOnBorrow(false);
			cs.setTestOnReturn(false);
			ldapContextPoolMap.put(desc.getId(), cs);
			*/
			ldapContextPoolMap.put(desc.getId(), lcs);
			mto = lcs;
		}
		Log.info(String.format("成功创建LDAP上下文池(%s)", desc.getId()));
		return mto;
	}

	@Override
	protected void beforeRemoveDescriptor(LdapContextDescriptor descriptor) {
		synchronized (this) {
			ldapContextPoolMap.remove(descriptor.getId());
//			PoolingContextSource pool = ldapContextPoolMap.remove(descriptor.getId());
//			if (pool != null)
//				try {
//					pool.destroy();
//				} catch (Throwable e) {
//					logger.error(String.format("关闭LDAP上下文池(%s)时发生错误(%s)", descriptor.toString(), e.getMessage()), e);
//				}
		}
		Log.info(String.format("完成LDAP上下文池(%s)关闭", descriptor.getId()));
	}

	@Override
	protected void postRemoveDescriptor(LdapContextDescriptor descriptor) {
	}

	@Override
	protected void beforeLoadConfiguredDescriptor(LdapContextDescriptor descriptor) {
		doAddFactoryToPool(descriptor);
	}

	@Override
	protected Properties beforeAddDescriptor(LdapContextDescriptor descriptor) {
		doAddFactoryToPool(descriptor);
		Properties props = new Properties();
		props.setProperty("host", descriptor.getHost());
		props.setProperty("port", Integer.valueOf(descriptor.getPort()).toString());
		props.setProperty("conntectType", descriptor.getConntectType().name());
		props.setProperty("baseDN", descriptor.getBaseDN());
		props.setProperty("userDN", descriptor.getUserDN());
		props.setProperty("userPassword", descriptor.getUserPassword());
		props.setProperty("validateDN", descriptor.getValidateDN());
		props.setProperty("title", descriptor.getTitle());
		props.setProperty("description", descriptor.getDescription());
		props.setProperty("poolMaxActive", Integer.valueOf(descriptor.getPoolMaxActive()).toString());
		props.setProperty("poolMaxIdle", Integer.valueOf(descriptor.getPoolMaxIdle()).toString());
		props.setProperty("poolMinIdle", Integer.valueOf(descriptor.getPoolMinIdle()).toString());
		props.setProperty("pooled", Boolean.valueOf(descriptor.isPooled()).toString());
		props.setProperty("ordinal", Integer.valueOf(descriptor.getOrdinal()).toString());
		return props;
	}

	@Override
	protected void setupDescriptorIdentifier(LdapContextDescriptor descriptor, String id) {
		descriptor.setId(id);
	}

	@Override
	protected void validateDescriptor(LdapContextDescriptor descriptor) {
		Assert.notNull(descriptor.getConntectType(), "必须指定LDAP连接类型");
		Assert.hasText(descriptor.getHost(), "必须指定LDAP主机地址");
		Assert.isTrue(descriptor.getPort() > 0, "LDAP端口无效");
		// Assert.hasText(descriptor.getBaseDN(), "必须指定LDAP上下文BaseDN");
		if (descriptor.getAuthenticationType() == LdapBindAuthenticationType.simple) {
			Assert.hasText(descriptor.getUserDN(), "必须指定验证用户CN");
			Assert.hasText(descriptor.getUserPassword(), "必须指定验证用户密码");
		} else if (descriptor.getAuthenticationType() == LdapBindAuthenticationType.strong) {
			throw new IllegalArgumentException("暂时不支持srong类型验证。");
		}

		Assert.isTrue(StringUtils.hasText(descriptor.getBaseDN()) || StringUtils.hasText(descriptor.getUserDN())
				|| StringUtils.hasText(descriptor.getValidateDN()), "连接参数为设置baseDN或者userDN时必须设定validateDN");

		if (!StringUtils.hasText(descriptor.getBaseDN()) && !StringUtils.hasText(descriptor.getUserDN()))
			if (descriptor.getConntectType() == LdapConntectType.tls) {
				throw new IllegalArgumentException("暂时不支持TLS安全连接");
			}
		DirContext ctx = null;
		try {
			LdapContextSource lcs = descriptor.createContextSource();
			ctx =  descriptor.isWriteable() ? lcs.getReadWriteContext() : lcs.getReadOnlyContext();
		} catch (Throwable e) {
			throw new IllegalArgumentException(e.getMessage(), e);
		} finally {
			if (ctx != null)
				try {
					ctx.close();
				} catch (Throwable e) {
				}
		}
	}

	@Override
	protected String resolveDescriptorIdentifier(LdapContextDescriptor descriptor) {
		String id = StringUtils.hasText(descriptor.getId()) ? descriptor.getId() : descriptor.getLdapProviderURL();
		return id.replaceAll(":", "-").replaceAll("\\/", ".").replace("=", "_").replace(",", "~");
	}

	public ContextSource addLdapContextSource(LdapContextDescriptor descriptor) {
		return getLdapContextSource(addDescriptor(descriptor));
	}

	public void removeLdapContextDataSource(LdapContextDescriptor descriptor) {
		removeDescriptor(descriptor);
	}

	public void removeLdapContextDataSource(String id) {
		removeDescriptor(id);
	}

	public ContextSource getLdapContextSource(LdapContextDescriptor desc) throws RuntimeException {
		return getLdapContextSource(resolveDescriptorIdentifier(desc));
	}

	public ContextSource getLdapContextSource(String id) throws RuntimeException {
		if (!hasLdapContextSource(id))
			throw new IllegalArgumentException(String.format("找不到Ldap上下文池(%s)", id));
		return ldapContextPoolMap.get(id);
	}

	public boolean hasLdapContextSource(LdapContextDescriptor desc) {
		return hasLdapContextSource(resolveDescriptorIdentifier(desc));
	}

	public boolean hasLdapContextSource(String id) {
		return hasDescriptor(id);
	}

	public DirContext getLdapContext(LdapContextDescriptor desc) throws RuntimeException {
		return getLdapContext(resolveDescriptorIdentifier(desc));
	}

	public DirContext getLdapContext(String id) throws RuntimeException {
		Assert.isTrue(hasLdapContext(id));
		ContextSource pool = getLdapContextSource(id);
		LdapContextDescriptor desc = getDescriptor(id);
		return desc.isWriteable() ? pool.getReadWriteContext() : pool.getReadOnlyContext();
	}

	public boolean hasLdapContext(LdapContextDescriptor desc) {
		return hasDescriptor(desc);
	}

	public boolean hasLdapContext(String id) {
		return hasDescriptor(id);
	}

	public void afterPropertiesSet() throws Exception {
		System.setProperty("javax.net.ssl.trustStore", StringUtils.hasText(getCaStoreLocation()) ? getCaStoreLocation()
				: defaultCaStore);
	}
}
